# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
#
# Top-level Makefile for chaps.
#
# Setting LINUX_BUILD=1 will build chaps for a standard (non-Chrome OS) linux
# system.

# Pull in chromium os defaults
PWD ?= $(CURDIR)
OUT ?= $(PWD)/build-opt-local

include common.mk

ifeq ($(LINUX_BUILD), 1)
  PLATFORM = linux
else
  PLATFORM = chromeos
endif

PKG_CONFIG ?= pkg-config
DBUSXX_XML2CPP = dbusxx-xml2cpp
PROTOC = protoc
INSTALL ?= install
INSTALL_DATA = $(INSTALL) -m 0644
BUILD_PAM_MODULE ?= $(LINUX_BUILD)
LIB_DIR ?= /usr/lib
PAM_MODULE_DIR ?= /lib/security

PC_DEPS = dbus-c++-1 protobuf-lite openssl \
	libchrome-$(BASE_VER) libchromeos-$(BASE_VER)
PC_CFLAGS := $(shell $(PKG_CONFIG) --cflags $(PC_DEPS))
PC_LIBS := $(shell $(PKG_CONFIG) --libs $(PC_DEPS))

CXXFLAGS += -I$(SRC)/.. -I$(OUT) $(PC_CFLAGS) -DNDEBUG -std=gnu++11 -fexceptions
LDLIBS += $(PC_LIBS)

# Test if libmemenv is available, and whether libsnappy is required to
# compile LevelDB.
MEMENV_LIB := $(call check_cxx,-lmemenv)
ifneq ($(MEMENV_LIB), -lmemenv)
  CPPFLAGS += -DNO_MEMENV
endif
LEVELDB_LIBS := $(call check_libs_cxx, \
	'\#include<leveldb/db.h>\nint main(){\
	 leveldb::Options options; leveldb::DB::Open(options, 0, 0);\
	 return 0;}',\
	 -lleveldb $(MEMENV_LIB), -lleveldb $(MEMENV_LIB) -lsnappy)

# Test if libmetric is available
METRICS_LIB := $(call check_cxx,-lmetrics)
ifneq ($(METRICS_LIB), -lmetrics)
  CPPFLAGS += -DNO_METRICS
endif

DBUS_ADAPTORS_DIR = $(OUT)/chaps/dbus_adaptors
DBUS_PROXIES_DIR = $(OUT)/chaps/dbus_proxies

# Rules for Generated Code
$(DBUS_PROXIES_DIR)/chaps_interface.h: $(SRC)/chaps_interface.xml
	mkdir -p $(DBUS_PROXIES_DIR)
	$(DBUSXX_XML2CPP) $< --proxy=$@
clean: CLEAN($(DBUS_PROXIES_DIR)/chaps_interface.h)

chaps_client.o.depends \
chaps_proxy.o.depends \
chapsd_test.o.depends \
chaps.o.depends \
chaps_event_generator.o.depends \
token_manager_client.o.depends: $(DBUS_PROXIES_DIR)/chaps_interface.h

$(DBUS_ADAPTORS_DIR)/chaps_interface.h: $(SRC)/chaps_interface.xml
	mkdir -p $(DBUS_ADAPTORS_DIR)
	$(DBUSXX_XML2CPP) $< --adaptor=$@
clean: CLEAN($(DBUS_ADAPTORS_DIR)/chaps_interface.h)

chaps_adaptor.o.depends \
chapsd.o.depends : $(DBUS_ADAPTORS_DIR)/chaps_interface.h

PROTO_DIR = $(OUT)/chaps/proto_bindings

$(PROTO_DIR)/attributes.pb.h \
$(PROTO_DIR)/attributes.pb.cc: $(SRC)/attributes.proto
	mkdir -p $(PROTO_DIR)
	$(PROTOC) -I$(SRC) --cpp_out=$(OUT)/chaps/proto_bindings $<
clean: CLEAN($(PROTO_DIR)/attributes.pb.h $(PROTO_DIR)/attributes.pb.cc)
$(eval $(call add_object_rules,$(PROTO_DIR)/attributes.pb.o,CXX,cc,CXXFLAGS))

object_pool_impl.o.depends \
object_pool_test.o.depends \
attributes.o.depends: $(PROTO_DIR)/attributes.pb.h

# Common Files
COMMON_OBJS = chaps_utility.o $(PROTO_DIR)/attributes.pb.o attributes.o

# Chaps Daemon
chapsd_OBJS = $(COMMON_OBJS) \
              chapsd.o \
              chaps_service.o \
              chaps_service_redirect.o \
              chaps_adaptor.o \
              isolate_$(PLATFORM).o \
              slot_manager_impl.o \
              session_impl.o \
              object_impl.o \
              object_policy_common.o \
              object_policy_data.o \
              object_policy_cert.o \
              object_policy_key.o \
              object_policy_public_key.o \
              object_policy_private_key.o \
              object_policy_secret_key.o \
              object_pool_impl.o \
              platform_globals_$(PLATFORM).o \
              tpm_utility_impl.o \
              chaps_factory_impl.o \
              object_store_impl.o \
              opencryptoki_importer.o
chapsd_LIBS = -ldl -ltspi $(LEVELDB_LIBS) $(METRICS_LIB)
CXX_BINARY(chapsd): $(chapsd_OBJS)
CXX_BINARY(chapsd): LDLIBS += $(chapsd_LIBS)
clean: CLEAN(chapsd)
all: CXX_BINARY(chapsd)

# Chaps DBus Configuration
ifeq ($(LINUX_BUILD),1)
DBUS_POLICY = "context=\"default\""
else
DBUS_POLICY = "group=\"pkcs11\""
endif
DBUS_FLAGS := $(call check_compile_cxx, \
	'\#include <dbus-c++/dbus.h>\nint main(){\
	DBus::Connection::SystemBus().acquire_name("dummy");\
	return 0;}',,-DNO_DBUS_ACQUIRE_NAME)
CXXFLAGS += $(DBUS_FLAGS)

.PHONY: $(OUT)/org.chromium.Chaps.conf
$(OUT)/org.chromium.Chaps.conf :
	sed -e "s/@POLICY_PERMISSIONS@/$(DBUS_POLICY)/" \
               $(SRC)/org.chromium.Chaps.conf.in > $@
all: $(OUT)/org.chromium.Chaps.conf
clean: CLEAN($(OUT)/org.chromium.Chaps.conf)

# Chaps Client Library
libchaps_OBJS = $(COMMON_OBJS) chaps.o chaps_proxy.o token_manager_client.o \
                isolate_$(PLATFORM).o
CXX_LIBRARY(libchaps.so): $(libchaps_OBJS)
clean: CLEAN(libchaps.so)
all: CXX_LIBRARY(libchaps.so)

ifneq ($(CHAPS_VERSION_MAJOR),)
ifneq ($(CHAPS_VERSION_MINOR),)
CHAPS_VERSION=$(CHAPS_VERSION_MAJOR).$(CHAPS_VERSION_MINOR)
# Also build a version with SONAME set.
CXX_LIBRARY(libchaps.so.$(CHAPS_VERSION)): $(libchaps_OBJS)
CXX_LIBRARY(libchaps.so.$(CHAPS_VERSION)): LDFLAGS += -Wl,-soname,libchaps.so.$(CHAPS_VERSION_MAJOR)
clean: CLEAN(libchaps.so.$(CHAPS_VERSION))
all: CXX_LIBRARY(libchaps.so.$(CHAPS_VERSION))
endif
endif

# Chaps Client CLI App
chaps_client_OBJS = $(COMMON_OBJS) chaps_client.o chaps_proxy.o \
                    token_manager_client.o isolate_$(PLATFORM).o
CXX_BINARY(chaps_client): $(chaps_client_OBJS)
clean: CLEAN(chaps_client)
all: CXX_BINARY(chaps_client)

# PKCS #11 Replay Utility
p11_replay_OBJS = p11_replay.o
CXX_BINARY(p11_replay): $(p11_replay_OBJS) CXX_LIBRARY(libchaps.so)
clean: CLEAN(p11_replay)
all: CXX_BINARY(p11_replay)

ifeq ($(BUILD_PAM_MODULE), 1)
# Chaps PAM Module
pamchaps_OBJS = $(COMMON_OBJS) chaps_pam_module.o chaps.o chaps_proxy.o \
                token_manager_client.o isolate_login_client.o pam_helper.o \
                token_file_manager_$(PLATFORM).o isolate_$(PLATFORM).o \
                platform_globals_$(PLATFORM).o
CXX_LIBRARY(pam_chaps.so): $(pamchaps_OBJS)
CXX_LIBRARY(pam_chaps.so): LDLIBS += -lpam
clean: CLEAN(pam_chaps.so)
all: CXX_LIBRARY(pam_chaps.so)
endif

ifeq ($(LINUX_BUILD), 1)
# Install chaps files into their locations on Linux.
install_files: all
	$(INSTALL) -D $(OUT)/chapsd $(DESTDIR)/usr/sbin/chapsd
	$(INSTALL) -D $(OUT)/chaps_client $(DESTDIR)/usr/bin/chaps_client
ifneq ($(CHAPS_VERSION),)
	$(INSTALL) -D $(OUT)/libchaps.so.$(CHAPS_VERSION) $(DESTDIR)$(LIB_DIR)/libchaps.so.$(CHAPS_VERSION)
	cd $(DESTDIR)$(LIB_DIR) && ln -s -f libchaps.so.$(CHAPS_VERSION) libchaps.so.$(CHAPS_VERSION_MAJOR)
	cd $(DESTDIR)$(LIB_DIR) && ln -s -f libchaps.so.$(CHAPS_VERSION_MAJOR) libchaps.so
else
	$(INSTALL) -D $(OUT)/libchaps.so $(DESTDIR)$(LIB_DIR)/libchaps.so
endif
	$(INSTALL_DATA) -D $(OUT)/org.chromium.Chaps.conf \
	  $(DESTDIR)/etc/dbus-1/system.d/org.chromium.Chaps.conf
	$(INSTALL_DATA) -D $(SRC)/org.chromium.Chaps.service \
	  $(DESTDIR)/usr/share/dbus-1/system-services/org.chromium.Chaps.service
ifeq ($(BUILD_PAM_MODULE), 1)
	$(INSTALL) -D $(OUT)/pam_chaps.so \
          $(DESTDIR)$(PAM_MODULE_DIR)/pam_chaps.so
	$(INSTALL_DATA) -D $(SRC)/pam_chaps.cfg \
	  $(DESTDIR)/usr/share/pam-configs/chaps
endif

# Perform all post-install operations for Chaps on Linux.
install_ops: install_files
	$(INSTALL) -m 700 -d $(DESTDIR)/var/lib/chaps/tokens/
	$(INSTALL) -m 755 -d $(DESTDIR)/var/lib/chaps/isolates/
ifeq ($(BUILD_PAM_MODULE), 1)
ifeq ($(DESTDIR),)
	pam-auth-update --package
endif
endif

install: install_files install_ops
endif

# Common Test Dependencies
MOCK_OBJS = chaps_factory_mock.o \
            object_mock.o \
            object_policy_mock.o \
            object_pool_mock.o \
            object_store_mock.o \
            pam_helper_mock.o \
            session_mock.o \
            slot_manager_mock.o \
            tpm_utility_mock.o \
            object_importer_mock.o

GMOCK_LIBS = -lgtest -lgmock

# QEMU will not run death-tests, generate RSA keys, or handle the import sample
# tarball. These tests need to be filtered out.
qemu_test_exclusions = *DeathTest* \
                       *ImportSample* \
                       TestSession.RSA* \
                       TestSession.KeyTypeMismatch \
                       TestSession.KeyFunctionPermission \
                       TestSession.BadKeySize \
                       TestSession.BadSignature
# We want $(space)=' '.
space :=
space +=
qemu_test_filter := -$(subst $(space),:,$(strip $(qemu_test_exclusions)))
tests: override GTEST_ARGS.qemu.arm := --gtest_filter=$(qemu_test_filter)

# Unit Tests
chaps_test_OBJS = $(COMMON_OBJS) isolate_$(PLATFORM).o chaps_test.o
chaps_test_LIBS = $(GMOCK_LIBS)
CXX_BINARY(chaps_test): $(chaps_test_OBJS) CXX_LIBRARY(libchaps.so)
CXX_BINARY(chaps_test): LDLIBS += $(chaps_test_LIBS)
clean: CLEAN(chaps_test)
tests: TEST(CXX_BINARY(chaps_test))

chaps_service_test_OBJS = $(COMMON_OBJS) $(MOCK_OBJS) \
                          chaps_service_test.o chaps_service.o \
                          isolate_$(PLATFORM).o
chaps_service_test_LIBS = $(GMOCK_LIBS)
CXX_BINARY(chaps_service_test): $(chaps_service_test_OBJS)
CXX_BINARY(chaps_service_test): LDLIBS += $(chaps_service_test_LIBS)
clean: CLEAN(chaps_service_test)
tests: TEST(CXX_BINARY(chaps_service_test))

slot_manager_test_OBJS = $(COMMON_OBJS) $(MOCK_OBJS) \
                         slot_manager_test.o slot_manager_impl.o \
                         isolate_$(PLATFORM).o
slot_manager_test_LIBS = $(GMOCK_LIBS)
CXX_BINARY(slot_manager_test): $(slot_manager_test_OBJS)
CXX_BINARY(slot_manager_test): LDLIBS += $(slot_manager_test_LIBS)
clean: CLEAN(slot_manager_test)
tests: TEST(CXX_BINARY(slot_manager_test))

session_test_OBJS = $(COMMON_OBJS) $(MOCK_OBJS) session_test.o session_impl.o
session_test_LIBS = $(GMOCK_LIBS)
CXX_BINARY(session_test): $(session_test_OBJS)
CXX_BINARY(session_test): LDLIBS += $(session_test_LIBS)
clean: CLEAN(session_test)
tests: TEST(CXX_BINARY(session_test))

object_test_OBJS = $(COMMON_OBJS) $(MOCK_OBJS) object_test.o object_impl.o
object_test_LIBS = $(GMOCK_LIBS)
CXX_BINARY(object_test): $(object_test_OBJS)
CXX_BINARY(object_test): LDLIBS += $(object_test_LIBS)
clean: CLEAN(object_test)
tests: TEST(CXX_BINARY(object_test))

object_policy_test_OBJS = $(COMMON_OBJS) $(MOCK_OBJS) \
                          object_policy_test.o \
                          object_policy_common.o \
                          object_policy_data.o \
                          object_policy_cert.o \
                          object_policy_key.o \
                          object_policy_public_key.o \
                          object_policy_private_key.o \
                          object_policy_secret_key.o
object_policy_test_LIBS = $(GMOCK_LIBS)
CXX_BINARY(object_policy_test): $(object_policy_test_OBJS)
CXX_BINARY(object_policy_test): LDLIBS += $(object_policy_test_LIBS)
clean: CLEAN(object_policy_test)
tests: TEST(CXX_BINARY(object_policy_test))

object_pool_test_OBJS = $(COMMON_OBJS) $(MOCK_OBJS) \
                        object_pool_test.o object_pool_impl.o
object_pool_test_LIBS = $(GMOCK_LIBS)
CXX_BINARY(object_pool_test): $(object_pool_test_OBJS)
CXX_BINARY(object_pool_test): LDLIBS += $(object_pool_test_LIBS)
clean: CLEAN(object_pool_test)
tests: TEST(CXX_BINARY(object_pool_test))

object_store_test_OBJS = $(COMMON_OBJS) object_store_test.o object_store_impl.o
object_store_test_LIBS = -lgtest $(LEVELDB_LIBS) $(METRICS_LIB)

CXX_BINARY(object_store_test): $(object_store_test_OBJS)
CXX_BINARY(object_store_test): LDLIBS += $(object_store_test_LIBS)
clean: CLEAN(object_store_test)
tests: TEST(CXX_BINARY(object_store_test))

opencryptoki_importer_test_OBJS = $(COMMON_OBJS) $(MOCK_OBJS) \
                                  opencryptoki_importer_test.o \
                                  opencryptoki_importer.o
opencryptoki_importer_test_LIBS = $(GMOCK_LIBS)
CXX_BINARY(opencryptoki_importer_test): $(opencryptoki_importer_test_OBJS)
CXX_BINARY(opencryptoki_importer_test): LDLIBS += \
                                        $(opencryptoki_importer_test_LIBS)
clean: CLEAN(opencryptoki_importer_test)
tests: $(OUT)/opencryptoki_sample_token.tgz
$(OUT)/opencryptoki_sample_token.tgz:
	cp $(SRC)/opencryptoki_sample_token.tgz $@
clean: CLEAN(opencryptoki_sample_token.tgz)
tests: TEST(CXX_BINARY(opencryptoki_importer_test))

isolate_login_client_test_OBJS = $(COMMON_OBJS) $(MOCK_OBJS) \
                                 isolate_$(PLATFORM).o \
                                 token_file_manager_$(PLATFORM).o \
                                 token_manager_client.o \
                                 chaps_proxy.o \
                                 isolate_login_client.o \
                                 isolate_login_client_test.o
isolate_login_client_test_LIBS = $(GMOCK_LIBS)
CXX_BINARY(isolate_login_client_test): $(isolate_login_client_test_OBJS)
CXX_BINARY(isolate_login_client_test): LDLIBS += \
                                       $(isolate_login_client_test_LIBS)
clean: CLEAN(isolate_login_client_test)
tests: TEST(CXX_BINARY(isolate_login_client_test))

ifeq ($(BUILD_PAM_MODULE), 1)
chaps_pam_module_test_OBJS = $(COMMON_OBJS) $(MOCK_OBJS) \
                             isolate_$(PLATFORM).o \
                             token_file_manager_$(PLATFORM).o \
                             platform_globals_$(PLATFORM).o \
                             token_manager_client.o \
                             isolate_login_client.o \
                             chaps_proxy.o \
                             pam_helper.o \
                             chaps_pam_module_test.o \
                             chaps_pam_module.o
chaps_pam_module_test_LIBS = $(GMOCK_LIBS)
CXX_BINARY(chaps_pam_module_test): $(chaps_pam_module_test_OBJS)
CXX_BINARY(chaps_pam_module_test): LDLIBS += \
                                   $(chaps_pam_module_test_LIBS)
clean: CLEAN(chaps_pam_module_test)
tests: TEST(CXX_BINARY(chaps_pam_module_test))
endif

import_random: override GTEST_ARGS := \
    --gtest_repeat=100 \
    --gtest_break_on_failure \
    --gtest_filter=RandomizedTests/TestImporterWithModifier.ImportSample/*
import_random: override GTEST_ARGS.qemu.arm := --gtest_filter=-*
import_random: TEST(CXX_BINARY(opencryptoki_importer_test))
more_tests: import_random

# Live Tests
# Note: These tests require a live system with gtest and gmock installed. These
# cannot be run without a real TPM and cannot be run with autotest. These tests
# do not need to be run regularly but may be useful in the future and so have
# been kept around. To run these tests:
# 1) Add the targets to 'all'.
# 2) Add 'dobin' instructions to the ebuild and install on target device.
# 3) On the target system, login as any user and open a shell.
# 4) Run 'sudo tpm_utility_test'.
# 5) Run 'sudo chapsd_test'.
# 6) Run 'chapsd_test --use_dbus'.
chapsd_test_OBJS = $(COMMON_OBJS) chapsd_test.o chaps_proxy.o \
                   chaps_service_redirect.o
chapsd_test_LIBS = -lgtest -ldl
CXX_BINARY(chapsd_test): $(chapsd_test_OBJS) CXX_LIBRARY(libchaps.so)
CXX_BINARY(chapsd_test): LDLIBS += $(chapsd_test_LIBS)
clean: CLEAN(chapsd_test)

tpm_utility_test_OBJS = $(COMMON_OBJS) $(MOCK_OBJS) \
                        tpm_utility_test.o \
                        tpm_utility_impl.o
tpm_utility_test_LIBS = $(GMOCK_LIBS) -ltspi
CXX_BINARY(tpm_utility_test): $(tpm_utility_test_OBJS)
CXX_BINARY(tpm_utility_test): LDLIBS += $(tpm_utility_test_LIBS)
clean: CLEAN(tpm_utility_test)
